import SEO from "../components/SEO";
import { TOC, TOCList, TOCLink } from "../components/TOC";
import { PropTable } from "../components/PropTable";
import { listboxDefs } from "../componentDefs";

<SEO
	title="Listbox"
	description="Accessible listbox dropdown component for React"
/>

# Listbox

<TOC>
	<TOCList>
		<TOCLink href="#listbox-1">Listbox</TOCLink>
		<TOCLink href="#listboxinput">ListboxInput</TOCLink>
		<TOCLink href="#listboxbutton">ListboxButton</TOCLink>
		<TOCLink href="#listboxarrow">ListboxArrow</TOCLink>
		<TOCLink href="#listboxpopover">ListboxPopover</TOCLink>
		<TOCLink href="#listboxlist">ListboxList</TOCLink>
		<TOCLink href="#listboxoption">ListboxOption</TOCLink>
		<TOCLink href="#listboxgroup">ListboxGroup</TOCLink>
		<TOCLink href="#listboxgrouplabel">ListboxGroupLabel</TOCLink>
		<TOCLink href="#uselistboxcontext">useListboxContext</TOCLink>
	</TOCList>
</TOC>

- Source: https://github.com/reach/reach-ui/tree/main/packages/listbox
- WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#Listbox

A listbox presents a list of selectable options in a popover, which is toggled on or off by a button. Visually and behaviorally it is similar to [`@reach/menu-button`](https://reach.tech/menu-button) except that `ListboxOption` components hold a value. In this sense they are more directly comparable to HTML select elements.

```jsx
// jsx-demo
(() => {
	function BasicExample() {
		let labelId = `taco-label--${useId()}`;
		return (
			<div>
				<VisuallyHidden id={labelId}>Choose a taco</VisuallyHidden>
				<Listbox aria-labelledby={labelId}>
					<ListboxOption value="default">Choose a taco</ListboxOption>
					<ListboxOption value="asada">Carne Asada</ListboxOption>
					<ListboxOption value="pollo" label="Pollo" disabled>
						Pollo <Tag>Sold Out!</Tag>
					</ListboxOption>
					<div style={{ background: "#ccc" }}>
						<ListboxOption value="pastor" label="Pastor">
							Pastor <Tag>Fan favorite!</Tag>
						</ListboxOption>
					</div>
					<ListboxOption value="lengua">Lengua</ListboxOption>
				</Listbox>
			</div>
		);
	}

	function Tag(props) {
		return (
			<span
				style={{
					display: "inline-block",
					lineHeight: 1,
					fontSize: 11,
					textTransform: "uppercase",
					fontWeight: "bolder",
					marginLeft: 6,
					padding: 4,
					background: "crimson",
					borderRadius: 2,
					color: "#fff",
				}}
				{...props}
			/>
		);
	}

	return <BasicExample />;
})();
```

You can use `Listbox` as a simple standalone component, or compose its parts with `ListboxInput`.

```jsx
// jsx-demo
(() => {
	function ComposedExample() {
		let labelId = `taco-label--${useId()}`;
		let [value, setValue] = React.useState("pollo");
		return (
			<div>
				<VisuallyHidden id={labelId}>Choose a taco</VisuallyHidden>
				<ListboxInput
					aria-labelledby={labelId}
					value={value}
					onChange={(value) => setValue(value)}
				>
					<ListboxButton arrow="▼" />
					<ListboxPopover>
						<ListboxList>
							<ListboxOption value="default">Choose a taco</ListboxOption>
							<ListboxOption value="asada">Carne Asada</ListboxOption>
							<ListboxOption value="pollo">Pollo</ListboxOption>
							<ListboxOption value="pastor">Pastor</ListboxOption>
							<ListboxOption value="lengua">Lengua</ListboxOption>
						</ListboxList>
						<div
							style={{
								padding: "10px 10px 0",
								marginTop: 10,
								borderTop: "1px solid gray",
							}}
						>
							<p>I really like tacos. I hope you enjoy them as well!</p>
						</div>
					</ListboxPopover>
				</ListboxInput>
			</div>
		);
	}

	return <ComposedExample />;
})();
```

## Installation

From the command line in your project directory, run `npm install @reach/listbox` or `yarn add @reach/listbox`. Then import the components and styles that you need:

```bash
npm install @reach/listbox
# or
yarn add @reach/listbox
```

```js
import {
	Listbox,
	ListboxInput,
	ListboxButton,
	ListboxPopover,
	ListboxList,
	ListboxOption,
} from "@reach/listbox";
import "@reach/listbox/styles.css";
```

## Usage

```jsx
// Basic listbox
function Example() {
	return (
		<Listbox defaultValue="popeyes">
			<ListboxOption value="bojangles">Bojangles'</ListboxOption>
			<ListboxOption value="churchs">Church's</ListboxOption>
			<ListboxOption value="kfc">KFC</ListboxOption>
			<ListboxOption value="popeyes">Popeyes</ListboxOption>
		</Listbox>
	);
}

// Composed listbox components
function ExampleComposed() {
	return (
		<ListboxInput defaultValue="popeyes">
			<ListboxButton />
			<ListboxPopover>
				<ListboxList>
					<ListboxOption value="bojangles">Bojangles'</ListboxOption>
					<ListboxOption value="churchs">Church's</ListboxOption>
					<ListboxOption value="kfc">KFC</ListboxOption>
					<ListboxOption value="popeyes">Popeyes</ListboxOption>
				</ListboxList>
			</ListboxPopover>
		</ListboxInput>
	);
}
```

## Component API

### Listbox

The wrapper component for the high-level listbox API.

```jsx
<div>
	<span id="my-label">Choose a topic</span>
	<Listbox aria-labelledby="my-label" defaultValue="comedy">
		<ListboxOption value="drama">Drama</ListboxOption>
		<ListboxOption value="comedy">Comedy</ListboxOption>
		<ListboxOption value="suspense">Suspense</ListboxOption>
		<ListboxOption value="horror">Horror</ListboxOption>
	</Listbox>
</div>
```

#### Controlled Listbox

If you want to control the state of the listbox's value, you can do so by passing [`value`](#listboxinput-value) and [`onChange`](#listboxinput-onchange) props. The value corresponds with the value of the selected `ListboxOption`.

```jsx
const [value, setValue] = React.useState("comedy");
return (
	<div>
		<span id="my-label">Choose a movie genre</span>
		<Listbox aria-labelledby="my-label" value={value} onChange={setValue}>
			<ListboxOption value="drama">Drama</ListboxOption>
			<ListboxOption value="comedy">Comedy</ListboxOption>
			<ListboxOption value="suspense">Suspense</ListboxOption>
			<ListboxOption value="horror">Horror</ListboxOption>
		</Listbox>
	</div>
);
```

#### Listbox Props

<PropTable componentName="Listbox" propDefs={listboxDefs.Listbox.propDefs} />

##### Listbox `arrow`

`arrow?: boolean | React.ReactNode`

Renders a text string or React node to represent an arrow inside the `ListboxButton`.

```jsx
<Listbox arrow /> // renders a default arrow character
<Listbox arrow={<span>▼</span>} /> // renders a component as an arrow character
```

If you want to customize the appearance and placement of the arrow inside the button further, you can drop down to the composed API and [render the button's children](#listboxbutton-children).

##### Listbox `button`

`button?: React.ReactNode | ((props: { value: string | null; label: string | null; }) => React.ReactNode)`

A render function or React node to to render the Listbox button's inner content. [See the API for the ListboxButton children prop for details.](#listboxbutton-children)

##### Listbox `children`

`children: React.ReactNode`

`Listbox` should accept `ListboxOption` components as children.

You can also pass arbitrary elements as needed, but be careful when passing elements that have semantic value into listbox directly (such as `button`). If you need such elements in the popover, chances are they either need to be nested inside an option or elsewhere outside of the list of options. In the latter case, use `ListboxInput` and the composed API.

##### Listbox `defaultValue`

`defaultValue?: string`

The default value of an uncontrolled listbox.

##### Listbox `disabled`

`disabled?: boolean`

Whether or not the listbox is disabled.

##### Listbox `form`

`form?: string`

The ID of a form associated with the listbox and its hidden input field. If Listbox is passed directly inside of its associated form this prop can be omitted so long as the `name` prop is used.

##### Listbox `name`

`name?: string`

The name used for the listbo input's form value.

##### Listbox `onChange`

`onChange?(newValue: string): void;`

The callback that fires when the listbox value changes.

##### Listbox `portal`

`portal?: boolean`

Whether or not the popover should be rendered inside a portal. Defaults to `true`.

##### Listbox `required`

`required?: boolean;`

Whether or not the listbox input's form field is required.

##### Listbox `value`

`value?: string`

The current value of a controlled listbox.

### ListboxInput

The top-level component and context provider for the listbox.

```jsx
<div>
	<span id="my-label">Choose a topic</span>
	<ListboxInput aria-labelledby="my-label" defaultValue="comedy">
		<ListboxButton />
		<ListboxPopover>
			<ListboxList>
				<ListboxOption value="drama">Drama</ListboxOption>
				<ListboxOption value="comedy">Comedy</ListboxOption>
				<ListboxOption value="suspense">Suspense</ListboxOption>
				<ListboxOption value="horror">Horror</ListboxOption>
			</ListboxList>
		</ListboxPopover>
	</ListboxInput>
</div>
```

#### ListboxInput CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-listbox-input] {
}
[data-reach-listbox-input][data-state="idle"] {
}
[data-reach-listbox-input][data-value="VALUE_REF"] {
}
```

#### ListboxInput Props

<PropTable
	componentName="ListboxInput"
	propDefs={listboxDefs.ListboxInput.propDefs}
/>

##### ListboxInput `children`

`children: React.ReactNode | ((props: { value: string | null; valueLabel: string | null; isExpanded: boolean; }) => React.ReactNode)`

The composed listbox expects to receive `ListboxButton` and `ListboxPopover` as children. You can also pass in arbitrary wrapper elements if desired.

If you want access to the listbox's current value and associated label, or its expanded state, you can use a render function.

```jsx
<ListboxInput>
	{({ value, valueLabel, isExpanded }) => (
		<>
			<ListboxButton>
				<span data-value={value}>{valueLabel}</span>
			</ListboxButton>
			<ListboxPopover>
				<ListboxList>
					<ListboxOption value="apple">Apple 🍏</ListboxOption>
					<ListboxOption value="orange">Orange 🍊</ListboxOption>
					<ListboxOption value="banana">Banana 🍌</ListboxOption>
				</ListboxList>
			</ListboxPopover>
		</>
	)}
</ListboxInput>
```

##### ListboxInput `defaultValue`

`defaultValue?: string`

The default value of an uncontrolled listbox.

##### ListboxInput `disabled`

`disabled?: boolean`

Whether or not the listbox is disabled.

##### ListboxInput `form`

`form?: string`

The ID of a form associated with the listbox and its hidden input field. If Listbox is passed directly inside of its associated form this prop can be omitted so long as the `name` prop is used.

##### ListboxInput `name`

`name?: string`

The name used for the listbo input's form value.

##### ListboxInput `onChange`

`onChange?(newValue: string): void;`

The callback that fires when the listbox value changes.

##### ListboxInput `required`

`required?: boolean;`

Whether or not the listbox input's form field is required.

##### ListboxInput `value`

`value?: string`

The current value of a controlled listbox.

### ListboxButton

The interactive toggle button that triggers the popover for the listbox.

#### ListboxButton CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-listbox-button] {
}
[data-reach-listbox-button][aria-expanded="true"] {
}
[data-reach-listbox-button][aria-disabled="true"] {
}
```

#### ListboxButton Props

<PropTable
	componentName="ListboxButton"
	propDefs={listboxDefs.ListboxButton.propDefs}
/>

##### ListboxButton `arrow`

`arrow?: boolean | React.ReactNode`

Renders a text string or React node to represent an arrow inside the button`.

##### ListboxButton `children`

`children: React.ReactNode | ((props: { value: string | null; label: string; isExpanded: boolean; }) => React.ReactNode)`

A render function or React node to to render the button's inner content.

By default, the button will display the text label of the selected option as its inner content. This label can be pulled from the option's inner text content or explicitly provided to the ListboxOption component via the label prop. If you want to render the button differently from its default, you must pass children.

##### Rendering ListboxButton on the server

It's important to note that the ListboxButton's default inner content cannot be server-side rendered. On the initial render, the button has no contextual information about the available options in a Listbox. As each ListboxOption is rendered, it is registered in a context object and updated at the top of the Listbox tree, which evaluates the options and their props to determine which option is selectable and which label to display inside the button. If you need the inner content of the button on the first render you must control the listbox's state and keep its options' values and labels in data at the top of the tree, and render the button directly via children.

```jsx
let options = { one: "One option", two: "Another option" };
let [value, setValue] = React.useState(options.one);
return (
	<ListboxInput>
		<ListboxButton>{options[value]}</ListboxButton>
		<ListboxPopover>
			<ListboxList>
				{Object.keys(options).map((option) => (
					<ListboxOption key={option} value={option} label={options[option]}>
						{options[option]}
					</ListboxOption>
				))}
			</ListboxList>
		</ListboxPopover>
	</ListboxInput>
);
```

### ListboxArrow

A wrapper component for an arrow to display in the `ListboxButton`.

You can use your own wrapper component if you prefer, but if its inner content contains anything that can be read by assistive devices you should use `aria-hidden` on the wrapper element, as the arrow should have no semantic value.

#### ListboxArrow CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-listbox-arrow] {
}
[data-reach-listbox-arrow][data-expanded] {
}
```

#### ListboxArrow Props

<PropTable
	componentName="ListboxArrow"
	propDefs={listboxDefs.ListboxArrow.propDefs}
/>

##### ListboxArrow `children`

`children: React.ReactNode | ((props: { isExpanded: boolean }) => React.ReactNode)`

Children to render as the listbox button's arrow. This can be a render function that accepts the listbox's `isExpanded` state as an argument.

### ListboxPopover

The popover containing the list of options.

#### ListboxPopover CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-listbox-popover] {
}
[data-reach-listbox-popover][hidden] {
}
```

#### ListboxPopover Props

<PropTable
	componentName="ListboxPopover"
	propDefs={listboxDefs.ListboxPopover.propDefs}
/>

##### ListboxPopover `children`

`children: React.ReactNode`

`ListboxPopover` expects to receive `ListboxList` as its children.

##### ListboxPopover `portal`

`portal?: boolean`

Whether or not the popover should be rendered inside a portal. Defaults to `true`.

##### ListboxPopover `position`

`position?(targetRect?: DOMRect | null; popoverRect?: DOMRect | null): React.CSSProperties`

The positioning function for the popover.

### ListboxList

The list containing all listbox options.

#### ListboxList CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-listbox-list] {
}
```

### ListboxOption

A selectable option for the listbox.

#### ListboxOption CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-listbox-option] {
	/* styles for all listbox options */
}
[data-reach-listbox-option][data-current-selected] {
	/* styles for the option matching the current value of the input */
}
[data-reach-listbox-option][data-current-nav] {
	/* styles for the option matching the user's navigation selection */
}
[data-reach-listbox-option][aria-disabled="true"] {
	/* styles for disabled listbox options */
}
```

#### ListboxOption Props

<PropTable
	componentName="ListboxOption"
	propDefs={listboxDefs.ListboxOption.propDefs}
/>

##### ListboxOption `disabled`

`disabled?: boolean`

Whether or not the option is disabled from selection and navigation.

##### ListboxOption `label`

`label?: string`

The option's human-readable label. This prop is optional but highly encouraged if your option has multiple text nodes that may or may not correlate with the intended value, or if the inner text is really long (all text will be read by a screen reader by default, which can create a confusing experience).

It is also useful if the inner text node begins with a character other than a readable letter (like an emoji or symbol) so that typeahead works as expected for the user.

##### ListboxOption `value`

`value: string`

The option's value. This will be passed into a hidden input field for use in forms when the option is selected.

### ListboxGroup

A group of related listbox options.

#### ListboxGroup CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-listbox-group] {
}
```

#### ListboxGroup Props

<PropTable
	componentName="ListboxGroup"
	propDefs={listboxDefs.ListboxGroup.propDefs}
/>

##### ListboxGroup `label`

`label?: string`

The text label to use for the listbox group. This can be omitted if a group contains a `ListboxGroupLabel` component. The label should always be human-readable.

### ListboxGroupLabel

A label identifier for a `ListboxGroup`. This can be used in lieu of the `label` prop in `ListboxGroup`.

```jsx
<ListboxGroup>
	<ListboxGroupLabel>Veggies</ListboxGroupLabel>
	<ListboxOption value="broccoli">Broccoli 🥦</ListboxOption>
	<ListboxOption value="carrot">Carrot 🥕</ListboxOption>
	<ListboxOption value="tomato">Tomato 🍅</ListboxOption>
</ListboxGroup>
```

#### ListboxGroupLabel CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-listbox-group-label] {
}
```

### useListboxContext

`function useListboxContext(): { id: string | undefined; isExpanded: boolean; value: string | null; valueLabel: string | null }`

A hook that exposes data for a given `ListboxContext` component to its descendants.
